#ifndef SHADER_H
#define SHADER_H

#include "ishader.h"
#include "tgaimage.h"
#include "model.h"
#include "rasterizer.h"
#include "mathengine.h"
#include "rendercontext.h"

#include <memory>

namespace raster{

using mathengine::Vec2f;
using mathengine::Vec3f;
using mathengine::Vec4f;
using mathengine::Mat;

#if 0
struct PhongShader : public IShader {
    Mat<float, 2, 3> varying_uv;  // 1 triangle uv coordinate per col
    Mat<float, 4, 3> varying_tri; // 1 triangle vertex coordinate (clip coordinate) per col
    Mat<float, 3, 3> varying_nrm; // normal per vertex to be interpolated by FS
    Mat<float, 3, 3> ndc_tri;     // triangle in normalized device coordinates

    ~PhongShader() {}
    // VS
    mathengine::Vec4f vertex(Rasterizer *rasterizer, int iface, int nthvert) override {
        if (!rasterizer) return {};
        auto uv = rasterizer->currentModel()->uv(iface, nthvert);
        varying_uv.setCol(nthvert, uv);

        // normal_eye =((ModelView)^{-1})^T * normal_obj
        // https://www.songho.ca/opengl/gl_normaltransform.html
#if 0
        auto m = rasterizer->currentRenderContext().getModel();
        auto v = rasterizer->currentRenderContext().getView();
        auto p = rasterizer->currentRenderContext().getProjection();
#endif
        auto mvp = rasterizer->currentRenderContext().getMVP();
        auto a = mvp.invert_transpose();
        auto b = mathengine::embed<float, 3, 4>(rasterizer->currentModel()->norm(iface, nthvert), 0.f);
        varying_nrm.setCol(nthvert, mathengine::proj<float, 4, 3>(a * b));

        Vec4f gl_Vertex = mvp * mathengine::embed<float, 3, 4>(rasterizer->currentModel()->vert(iface, nthvert), 1);
        varying_tri.setCol(nthvert, gl_Vertex); // clip space
        ndc_tri.setCol(nthvert, mathengine::proj<float, 4, 3>(gl_Vertex / gl_Vertex[3])); // ndc

        return gl_Vertex;
    }

    // FS
    bool fragment(Rasterizer *rasterizer, mathengine::Vec3f bar, raster::TGAColor &color) override {
        Vec3f bn = (varying_nrm * bar).normalize();
        Vec2f uv = varying_uv * bar;

        // ndc_tri = (p0 p1 p2)
        // = (p0_x p1_x p2_x
        //    p0_y p1_y p2_y
        //    p0_z p1_z p2_z)
        // A=(p0p1_x p0p1_y p0p1_z
        //    p0p2_x p0p2_y p0p2_z
        //    nx     ny     nz)
        // Ax = b
        // x = A^{-1}b
        Mat<float, 3, 3> A;
        A.setRow(0, ndc_tri.getCol(1) - ndc_tri.getCol(0));
        A.setRow(1, ndc_tri.getCol(2) - ndc_tri.getCol(0));
        A.setRow(2, bn);

        // Darboux basis: (i j n)
        // i = A^{-1}(u1-u0
        //            u2-u0
        //            0)
        // j = A^{-1}(v1-v0
        //            v2-v0
        //            0)
        // (u,v) is texture coordinate of interpolation point P
        Mat<float, 3, 3> AI = A.invert();
        const auto [u0, u1, u2] = std::make_tuple(varying_uv.getValue(0, 0),
                                                  varying_uv.getValue(0, 1),
                                                  varying_uv.getValue(0, 2));
        const auto [v0, v1, v2] = std::make_tuple(varying_uv.getValue(1, 0),
                                                  varying_uv.getValue(1, 1),
                                                  varying_uv.getValue(1, 2));
        Vec3f i = AI * Vec3f{u1 - u0, u2 - u0, 0};
        Vec3f j = AI * Vec3f{v1 - v0, v2 - v0, 0};

        Mat<float, 3, 3> B;
        B.setCol(0, i.normalize());
        B.setCol(1, j.normalize());
        B.setCol(2, bn);

        Vec3f n = (B * rasterizer->currentModel()->norm(uv)).normalize();

        float diff = std::max(0.f, n * rasterizer->lightDir()); // 有效漫反射光强I_{l,diff} = kd*I_{l}cosθ = kd*N*L
        color = rasterizer->currentModel()->diffuse(uv) * diff;
        return false;
    }
};
#else
struct PhongShader : public IShader {
    Mat<float, 2, 3> varying_uv;
    Mat<float, 4, 4> uniform_M;   // Projection * Model * View
    Mat<float, 4, 4> uniform_MIT; // (Projection * Model * View).invert_transpose

    PhongShader(const Mat<float,4,4> &m, const Mat<float,4,4> &mit) {
        uniform_M = m;
        uniform_MIT = mit;
    }

    Vec4f vertex(Rasterizer *rasterizer, int iface, int nthvert) override {
        if (!rasterizer) return {};
        auto uv = rasterizer->currentModel()->uv(iface, nthvert);
        varying_uv.setCol(nthvert, uv);

        Vec4f gl_Vertex = mathengine::embed<float, 3, 4>(rasterizer->currentModel()->vert(iface, nthvert));
        auto mvp        = rasterizer->currentRenderContext().getMVP();
        auto viewport   = rasterizer->currentRenderContext().getViewport();
        gl_Vertex       = viewport * mvp * gl_Vertex;
        return gl_Vertex;
    }

    // Ambient + Diffuse + Specular = Phong reflection
    bool fragment(Rasterizer *rasterizer, mathengine::Vec3f bar, raster::TGAColor &color) override {
        Vec2f uv = varying_uv * bar;
        Vec3f n  = mathengine::proj<float, 4, 3>(uniform_MIT * mathengine::embed<float, 3, 4>(rasterizer->currentModel()->norm(uv))).normalize();
        Vec3f l  = mathengine::proj<float, 4, 3>(uniform_M   * mathengine::embed<float, 3, 4>(rasterizer->lightDir()              )).normalize();
        Vec3f r  = (2.f * n * (n * l) - l).normalize(); // reflect light direction

        float ambient_intensity = 10;
        float diffuse_intensity = std::max(0.f, n * l);
        float spec_intensity    = std::pow(std::max(r.z(), 0.f), rasterizer->currentModel()->specular(uv).b);
        color = rasterizer->currentModel()->diffuse(uv);
        for (int i = 0; i < 3; i++) { // b,g,r
            color[i] = std::min<float>(
                ambient_intensity + color[i] * (diffuse_intensity + 0.6f * spec_intensity),
                255);
        }
        return false;
    }
};
#endif

struct GouraudShader : public IShader {
    Mat<float, 2, 3> varying_uv;
    Vec3f varying_intensity;

    ~GouraudShader() { }
    mathengine::Vec4f vertex(Rasterizer *rasterizer, int iface, int nthvert) override {
        if (!rasterizer) return {};
        auto uv = rasterizer->currentModel()->uv(iface, nthvert);
        varying_uv.setCol(nthvert, uv);

        // n * light_dir
        auto n = rasterizer->currentModel()->norm(iface, nthvert);
        auto light_dir = rasterizer->lightDir();
        varying_intensity[nthvert] = std::max(0.f,  n * light_dir);

        Vec4f gl_Vertex = mathengine::embed<float, 3, 4>(rasterizer->currentModel()->vert(iface, nthvert));
        auto mvp = rasterizer->currentRenderContext().getMVP();
        auto viewport = rasterizer->currentRenderContext().getViewport();
        gl_Vertex = viewport * mvp * gl_Vertex;
        return gl_Vertex; // no Homogeneous Division
    }

    bool fragment(Rasterizer *rasterizer, mathengine::Vec3f bar, raster::TGAColor &color) override {
        float intensity = varying_intensity * bar;
        Vec2f uv = varying_uv * bar;
        TGAColor diffuse = rasterizer->currentModel()->diffuse(uv);
        color = diffuse * intensity;
        return false;
    }
};

}

#endif // SHADER_H
